﻿// original Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
//
// This file is part of SharpMap.
// SharpMap is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// SharpMap is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with SharpMap; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
//
// adopted by asida@centrum.cz
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;

using System;
using System.Collections.Generic;
using System.Text;

namespace Petra.Gis.Geometry.Support
{
  /// <summary>
  /// represents orthogonal rectangular area in world coordinates given by 4 values of:
  /// left (minimal) horizontal geometry coordinate
  /// bottom (minimal) vertical geometry coordinate
  /// right (maximal) horizontal geometry coordinate
  /// top (maximal) vertical geometry coordinate
  /// </summary>
  public class BoundingBox : IEquatable<BoundingBox>
  {

    protected CoordinateList _bound;



    /// <summary>
    /// xaml constr.
    /// </summary>
    public BoundingBox() 
    {
      _bound = new CoordinateList(2, Dimension.dim2D);
    }





    /// <summary>
    /// Initializes a bounding box
    /// </summary>
    /// <remarks>
    /// In case min values are larger than max values, the parameters will be swapped to ensure correct min/max boundary
    /// </remarks>
    /// <param name="minX">left</param>
    /// <param name="minY">bottom</param>
    /// <param name="maxX">right</param>
    /// <param name="maxY">top</param>
    public BoundingBox(double left, double bottom, double right, double top)
      :this()
    {
      _bound[0].X = left;
      _bound[0].Y = bottom;
      _bound[1].X = right;
      _bound[1].Y = top;

      checkMinMax();
    }



    ///// <summary>
    ///// Initializes a bounding box
    ///// </summary>
    ///// <param name="lowerLeft">Lower left corner</param>
    ///// <param name="upperRight">Upper right corner</param>
    //public BoundingBox(Geometries.Point lowerLeft, Geometries.Point upperRight)
    //  : this(lowerLeft.X, lowerLeft.Y, upperRight.X, upperRight.Y)
    //{
    //}




    /// <summary>
    /// Initializes a new Bounding Box based on the bounds from a set of geometries
    /// </summary>
    /// <param name="objects">list of objects</param>
    public BoundingBox(GeometryBase[] geoms)
      :this()
    {
      if (geoms == null || geoms.Length == 0)
      {
        _bound[0] = null;
        _bound[1] = null;
        return;
      }

      _bound[0].X = geoms[0].BoundingBox.Min.X;
      _bound[0].Y = geoms[0].BoundingBox.Min.Y;
      _bound[1].X = geoms[0].BoundingBox.Max.X;
      _bound[1].Y = geoms[0].BoundingBox.Max.Y;

      checkMinMax();

      for (int i = 1; i < geoms.Length; i++)
      {
        BoundingBox box = geoms[i].BoundingBox;
        _bound[0].X = Math.Min(box.Left, this.Left);
        _bound[0].Y = Math.Min(box.Bottom, this.Bottom);
        _bound[1].X = Math.Max(box.Right, this.Right);
        _bound[1].Y = Math.Max(box.Top, this.Top);
      }
    }




    ///// <summary>
    ///// Initializes a new Bounding Box based on the bounds from a set of bounding boxes
    ///// </summary>
    ///// <param name="objects">list of objects</param>
    //public BoundingBox(GeometryBase[] geoms)
    //{
    //  if (geoms.Count == 0) { _bound[1] = null; _bound[0] = null; }
    //  else
    //  {
    //    _bound[0] = geoms[0].Min.Clone();
    //    _bound[1] = geoms[0].Max.Clone();
    //    for (int i = 1; i < objects.Count; i++)
    //    {
    //      _bound[0].X = Math.Min(geoms[i].Left, this.Left);
    //      _bound[0].Y = Math.Min(geoms[i].Bottom, this.Bottom);
    //      _bound[1].X = Math.Max(geoms[i].Right, this.Right);
    //      _bound[1].Y = Math.Max(geoms[i].Top, this.Top);
    //    }
    //  }
    //}


    
    
    /// <summary>
    /// Gets or sets the lower left corner.
    /// </summary>
    public PointD Min
    {
      get { return new PointD(_bound[0].X, _bound[0].Y); }
      //set { _bound[0] = value; }
    }




    /// <summary>
    /// Gets or sets the upper right corner.
    /// </summary>
    public PointD Max
    {
      get { return new PointD(_bound[1].X, _bound[1].Y); }
      //set { _bound[1] = value; }
    }




    /// <summary>
    /// Gets the left boundary
    /// </summary>
    public Double Left
    {
      get { return _bound[0].X; }
      set { _bound[0].X = value; }
    }




    /// <summary>
    /// Gets the right boundary
    /// </summary>
    public Double Right
    {
      get { return _bound[1].X; }
      set { _bound[1].X = value; }
    }




    /// <summary>
    /// Gets the top boundary
    /// </summary>
    public Double Top
    {
      get { return _bound[1].Y; }
      set { _bound[1].Y = value; }
    }




    /// <summary>
    /// Gets the bottom boundary
    /// </summary>
    public Double Bottom
    {
      get { return _bound[0].Y; }
      set { _bound[0].Y = value; }
    }




    /// <summary>
    /// Returns the width of the bounding box
    /// </summary>
    /// <returns>Width of boundingbox</returns>
    public double Width
    {
      get { return Math.Abs(_bound[1].X - _bound[0].X); }
    }




    /// <summary>
    /// Returns the height of the bounding box
    /// </summary>
    /// <returns>Height of boundingbox</returns>
    public double Height
    {
      get { return Math.Abs(_bound[1].Y - _bound[0].Y); }
    }




    ///// <summary>
    ///// Moves/translates the <see cref="BoundingBox"/> along the the specified vector
    ///// </summary>
    ///// <param name="vector">Offset vector</param>
    //public void Offset(Point vector)
    //{
    //  _bound[0] += vector;
    //  _bound[1] += vector;
    //}




    /// <summary>
    /// Checks whether min values are actually smaller than max values and in that case swaps them.
    /// </summary>
    private void checkMinMax()
    {
      if (_bound[0].X > _bound[1].X)
      {
        double tmp = _bound[0].X;
        _bound[0].X = _bound[1].X;
        _bound[1].X = tmp;
      }
      if (_bound[0].Y > _bound[1].Y)
      {
        double tmp = _bound[0].Y;
        _bound[0].Y = _bound[1].Y;
        _bound[1].Y = tmp;
      }
    }



    /// <summary>
    /// Determines whether the boundingbox intersects another boundingbox
    /// </summary>
    /// <param name="box"></param>
    /// <returns></returns>
    public bool Intersects(BoundingBox box)
    {
      return !(box.Left > this.Right ||
           box.Right < this.Left ||
           box.Bottom > this.Top ||
           box.Top < this.Bottom);
    }



    ///// <summary>
    ///// Returns true if this <see cref="BoundingBox"/> intersects the geometry
    ///// </summary>
    ///// <param name="g">Geometry</param>
    ///// <returns>True if intersects</returns>
    //public bool Intersects(GeometryBase g)
    //{
    //  return this.Touches(g);
    //}




    ///// <summary>
    ///// Returns true if this instance touches the <see cref="BoundingBox"/>
    ///// </summary>
    ///// <param name="r"><see cref="BoundingBox"/></param>
    ///// <returns>True it touches</returns>
    //public bool Touches(BoundingBox r)
    //{
    //  for (uint cIndex = 0; cIndex < 2; cIndex++)
    //  {
    //    if ((Min[cIndex] > r.Min[cIndex] && Min[cIndex] < r.Min[cIndex]) ||
    //        (Max[cIndex] > r.Max[cIndex] && Max[cIndex] < r.Max[cIndex]))
    //      return true;
    //  }
    //  return false;
    //}




    ///// <summary>
    ///// Returns true if this <see cref="BoundingBox"/> touches the geometry
    ///// </summary>
    ///// <param name="s">Geometry</param>
    ///// <returns>True if touches</returns>
    //public bool Touches(GeometryBase s)
    //{
    //  if (s is Point) return Touches(s as Point);
    //  throw new NotImplementedException("Touches: Not implemented on this geometry type");
    //}




    ///// <summary>
    ///// Returns true if this instance contains the <see cref="BoundingBox"/>
    ///// </summary>
    ///// <param name="r"><see cref="BoundingBox"/></param>
    ///// <returns>True it contains</returns>
    //public bool Contains(BoundingBox r)
    //{
    //  for (uint cIndex = 0; cIndex < 2; cIndex++)
    //    if (Min[cIndex] > r.Min[cIndex] || Max[cIndex] < r.Max[cIndex]) return false;

    //  return true;
    //}




    ///// <summary>
    ///// Returns true if this instance contains the geometry
    ///// </summary>
    ///// <param name="s"><see cref="BoundingBox"/></param>
    ///// <returns>True it contains</returns>
    //public bool Contains(GeometryBase s)
    //{
    //  if (s is Point) return Contains(s as Point);
    //  throw new NotImplementedException("Contains: Not implemented on these geometries");
    //}




    ///// <summary>
    ///// Returns true if this instance touches the <see cref="Point"/>
    ///// </summary>
    ///// <param name="p">Geometry</param>
    ///// <returns>True if touches</returns>
    //public bool Touches(Point p)
    //{
    //  for (uint cIndex = 0; cIndex < 2; cIndex++)
    //  {
    //    if ((Min[cIndex] > p[cIndex] && Min[cIndex] < p[cIndex]) ||
    //        (Max[cIndex] > p[cIndex] && Max[cIndex] < p[cIndex]))
    //      return true;
    //  }
    //  return false;
    //}




    /// <summary>
    /// Returns the area of the BoundingBox
    /// </summary>
    /// <returns>Area of box</returns>
    public double GetArea()
    {
      return Width * Height;
    }




    ///// <summary>
    ///// Gets the intersecting area between two boundingboxes
    ///// </summary>
    ///// <param name="r">BoundingBox</param>
    ///// <returns>Area</returns>
    //public double GetIntersectingArea(BoundingBox r)
    //{
    //  uint cIndex;
    //  for (cIndex = 0; cIndex < 2; cIndex++)
    //    if (Min[cIndex] > r.Max[cIndex] || Max[cIndex] < r.Min[cIndex]) return 0.0;

    //  double ret = 1.0;
    //  double f1, f2;

    //  for (cIndex = 0; cIndex < 2; cIndex++)
    //  {
    //    f1 = Math.Max(Min[cIndex], r.Min[cIndex]);
    //    f2 = Math.Min(Max[cIndex], r.Max[cIndex]);
    //    ret *= f2 - f1;
    //  }
    //  return ret;
    //}





    /// <summary>
    /// Computes the joined boundingbox of this instance and another boundingbox
    /// </summary>
    /// <param name="box">Boundingbox to join with</param>
    /// <returns>Boundingbox containing both boundingboxes</returns>
    public BoundingBox Join(BoundingBox box)
    {
      if (box == null)
        return this.Clone();
      else
        return new BoundingBox(Math.Min(this.Left, box.Left), Math.Min(this.Bottom, box.Bottom),
                     Math.Max(this.Right, box.Right), Math.Max(this.Top, box.Top));
    }




    /// <summary>
    /// Computes the joined boundingbox of two boundingboxes
    /// </summary>
    /// <param name="box1"></param>
    /// <param name="box2"></param>
    /// <returns></returns>
    public static BoundingBox Join(BoundingBox box1, BoundingBox box2)
    {
      if (box1 == null && box2 == null)
        return null;
      else if (box1 == null)
        return box2.Clone();
      else
        return box1.Join(box2);
    }




    /// <summary>
    /// Computes the joined <see cref="BoundingBox"/> of an array of boundingboxes.
    /// </summary>
    /// <param name="boxes">Boxes to join</param>
    /// <returns>Combined BoundingBox</returns>
    public static BoundingBox Join(BoundingBox[] boxes)
    {
      if (boxes == null) return null;
      if (boxes.Length == 1) return boxes[0];
      BoundingBox box = boxes[0].Clone();
      for (int i = 1; i < boxes.Length; i++)
        box = box.Join(boxes[i]);
      return box;
    }




    ///// <summary>
    ///// Increases the size of the boundingbox by the givent amount in all directions
    ///// </summary>
    ///// <param name="amount">Amount to grow in all directions</param>
    //public BoundingBox Grow(double amount)
    //{
    //  BoundingBox box = this.Clone();
    //  box.Left -= amount;
    //  box.Bottom -= amount;
    //  box.Right += amount;
    //  box.Top += amount;
    //  box.CheckMinMax();
    //  return box;
    //}




    ///// <summary>
    ///// Increases the size of the boundingbox by the givent amount in horizontal and vertical directions
    ///// </summary>
    ///// <param name="amountInX">Amount to grow in horizontal direction</param>
    ///// <param name="amountInY">Amount to grow in vertical direction</param>
    //public BoundingBox Grow(double amountInX, double amountInY)
    //{
    //  BoundingBox box = this.Clone();
    //  box.Left -= amountInX;
    //  box.Bottom -= amountInY;
    //  box.Right += amountInX;
    //  box.Top += amountInY;
    //  box.CheckMinMax();
    //  return box;
    //}




    /// <summary>
    /// Checks whether a point lies within the bounding box
    /// </summary>
    /// <param name="p">Point</param>
    /// <returns>true if point is within</returns>
    public bool Contains(PointD p)
    {
      if (this.Right < p.X)
        return false;
      if (this.Left > p.X)
        return false;
      if (this.Top < p.Y)
        return false;
      if (this.Bottom > p.Y)
        return false;
      return true;
    }




    ///// <summary> 
    ///// Computes the minimum distance between this and another <see cref="BoundingBox"/>.
    ///// The distance between overlapping bounding boxes is 0.  Otherwise, the
    ///// distance is the Euclidean distance between the closest points.
    ///// </summary>
    ///// <param name="box">Box to calculate distance to</param>
    ///// <returns>The distance between this and another <see cref="BoundingBox"/>.</returns>
    //public virtual double Distance(BoundingBox box)
    //{
    //  double ret = 0.0;
    //  for (uint cIndex = 0; cIndex < 2; cIndex++)
    //  {
    //    double x = 0.0;

    //    if (box.Max[cIndex] < Min[cIndex]) x = Math.Abs(box.Max[cIndex] - Min[cIndex]);
    //    else if (Max[cIndex] < box.Min[cIndex]) x = Math.Abs(box.Min[cIndex] - Max[cIndex]);
    //    ret += x * x;
    //  }
    //  return Math.Sqrt(ret);
    //}




    ///// <summary>
    ///// Computes the minimum distance between this BoundingBox and a <see cref="Point"/>
    ///// </summary>
    ///// <param name="p"><see cref="Point"/> to calculate distance to.</param>
    ///// <returns>Minimum distance.</returns>
    //public virtual double Distance(Point p)
    //{
    //  double ret = 0.0;

    //  for (uint cIndex = 0; cIndex < 2; cIndex++)
    //  {
    //    if (p[cIndex] < Min[cIndex]) ret += Math.Pow(Min[cIndex] - p[cIndex], 2.0);
    //    else if (p[cIndex] > Max[cIndex]) ret += Math.Pow(p[cIndex] - Max[cIndex], 2.0);
    //  }

    //  return Math.Sqrt(ret);
    //}




    ///// <summary>
    ///// Intersection scalar (used for weighting in building the tree) 
    ///// </summary>
    //public uint LongestAxis
    //{
    //  get
    //  {
    //    SharpMap.Geometries.Point boxdim = this.Max - this.Min;
    //    uint la = 0; // longest axis
    //    double lav = 0; // longest axis length
    //    // for each dimension  
    //    for (uint ii = 0; ii < 2; ii++)
    //    {
    //      // check if its longer
    //      if (boxdim[ii] > lav)
    //      {
    //        // store it if it is
    //        la = ii;
    //        lav = boxdim[ii];
    //      }
    //    }
    //    return la;
    //  }
    //}





    /// <summary>
    /// Returns the center of the bounding box
    /// </summary>
    public PointD GetCentroid()
    {
      return new PointD((this._bound[0].X + this._bound[1].X) * .5f, (this._bound[0].Y + this._bound[1].Y) * .5f);
    }




    /// <summary>
    /// Creates a copy of the BoundingBox
    /// </summary>
    /// <returns></returns>
    public BoundingBox Clone()
    {
      return new BoundingBox(this._bound[0].X, this._bound[0].Y, this._bound[1].X, this._bound[1].Y);
    }




    ///// <summary>
    ///// Returns a string representation of the boundingbox as LowerLeft + UpperRight formatted as "MinX,MinY MaxX,MaxY"
    ///// </summary>
    ///// <returns>MinX,MinY MaxX,MaxY</returns>
    //public override string ToString()
    //{
    //  return String.Format(SharpMap.Map.numberFormat_EnUS, "{0},{1} {2},{3}", this.Left, this.Bottom, this.Right, this.Top);
    //}


    #region IEquatable<BoundingBox> Members

    /// <summary>
    /// Indicates whether the current object is equal to another object of the same type.
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public override bool Equals(object obj)
    {
      BoundingBox box = obj as BoundingBox;
      if (obj == null) return false;
      else return Equals(box);
    }

    /// <summary>
    /// Returns a hash code for the specified object
    /// </summary>
    /// <returns>A hash code for the specified object</returns>
    public override int GetHashCode()
    {
      return _bound[0].GetHashCode() ^ _bound[1].GetHashCode();
    }

    /// <summary>
    /// Checks whether the values of this instance is equal to the values of another instance.
    /// </summary>
    /// <param name="other"><see cref="BoundingBox"/> to compare to.</param>
    /// <returns>True if equal</returns>
    public bool Equals(BoundingBox other)
    {
      if (other == null) return false;
      return this.Left == other.Left && this.Right == other.Right && this.Top == other.Top && this.Bottom == other.Bottom;
    }

    #endregion



    public override string ToString()
    {
      return _bound[0].X.ToString() + ";" +
             _bound[0].Y.ToString() + ";" +
             _bound[1].X.ToString() + ";" +
             _bound[1].Y.ToString();
    }


    public static BoundingBox Parse(string inpStr)
    {
      string[] bounds = inpStr.Split(';');

      // not parsed, try space
      if (bounds.Length != 4)
      {
        bounds = inpStr.Split(' ');
        if (bounds.Length != 4)
          throw new ArgumentException("invalid bbox string " + inpStr);
      }

      return new BoundingBox(
        System.Convert.ToDouble(bounds[0]),
        System.Convert.ToDouble(bounds[1]),
        System.Convert.ToDouble(bounds[2]),
        System.Convert.ToDouble(bounds[3])
        );


    }
  }
}
